/*******************************************************************************
 * Copyright (c) 2006, 2015 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.examples.dsf.filebrowser;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;


/**
 * File view model node which returns file elements that are found in the directory 
 * specified by the parent element.  The child nodes of this node are fixed to 
 * reference this element, and therefore this node will recursively populate 
 * the contents of the tree reflecting the underlying filesystem directories.
 * <br>
 * Note: this node does NOT sub-class the {@link org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMNode}
 */
@SuppressWarnings("restriction")
class FileVMNode 
    implements IElementLabelProvider, IVMNode
{
    /**
     * Reference to the viewer model provider.  It's mainly used to access the
     * viewer model adapter and its executor.
     */
    private final FileBrowserVMProvider fProvider;
    
    public FileVMNode(FileBrowserVMProvider provider) {
        fProvider = provider;
    }

    @Override
    public String toString() {
        return "FileVMNode";  
    }


    @Override
    public void dispose() {
        // All resources garbage collected.
    }
    
    public void setChildNodes(IVMNode[] childNodes) {
        throw new UnsupportedOperationException("This node does not support children."); //$NON-NLS-1$
    }

    /** 
     * List of child nodes containing only a reference to this.
     */
    private final IVMNode[] fChildNodes = { this };

    public IVMNode[] getChildNodes() {
        return fChildNodes;
    }
    
    @Override
    public void update(final IHasChildrenUpdate[] updates) {
        new Job("") { //$NON-NLS-1$
            {
                setSystem(true);
                setPriority(INTERACTIVE);
            }
                
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                for (IHasChildrenUpdate update : updates) {
                    /*
                     * Do not retrieve directory contents just to mark the plus
                     * sign in the tree.  If it's a directory, just assume that
                     * it has children.
                     */
                    FileVMContext vmc = (FileVMContext)update.getElement();
                    update.setHasChilren(vmc.getFile().isDirectory());
                    update.done();
                }
                
                return Status.OK_STATUS;
            }
        }.schedule();
    }
    
    @Override
    public void update(final IChildrenCountUpdate[] updates) {
        new Job("") { //$NON-NLS-1$
            {
                setSystem(true);
                setPriority(INTERACTIVE);
            }
                
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                for (IChildrenCountUpdate update : updates) {
                    update.setChildCount(getFiles(update).length);
                    update.done();
                }                
                return Status.OK_STATUS;
            }
        }.schedule();
    }
    
    @Override
    public void update(final IChildrenUpdate[] updates) {
        new Job("") { //$NON-NLS-1$
            {
                setSystem(true);
                setPriority(INTERACTIVE);
            }
                
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                for (IChildrenUpdate update : updates) {
                    File[] files = getFiles(update);
                    int offset = update.getOffset() != -1 ? update.getOffset() : 0;
                    int length = update.getLength() != -1 ? update.getLength() : files.length;
                    for (int i = offset; (i < files.length) && (i < (offset + length)); i++) {
                        update.setChild(new FileVMContext(FileVMNode.this, files[i]), i);
                    }
                    update.done();
                }                
                return Status.OK_STATUS;
            }
        }.schedule();
    }
    
    @Override
    public void update(final ILabelUpdate[] updates) {
        new Job("") { //$NON-NLS-1$
            {
                setSystem(true);
                setPriority(INTERACTIVE);
            }
                
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                for (ILabelUpdate update : updates) {
                    update.setLabel(getLabel((FileVMContext)update.getElement()), 0);
                    update.done();
                }
                
                return Status.OK_STATUS;
            }
        }.schedule();
    }

    private static final File[] EMPTY_FILE_LIST = new File[0];

    /**
     * Retrieves the list of files for this node.  The list of files is based 
     * on the parent element in the tree, which must be of type FileVMC. 
     * 
     * @param update Update object containing the path (and the parent element) 
     * in the tree viewer.
     * @return List of files contained in the directory specified in the 
     * update object.  An empty list if the parent element is not a directory. 
     * @throws ClassCastException If the parent element contained in the update 
     * is NOT of type FileVMC. 
     */
    private File[] getFiles(IViewerUpdate update) {
        FileVMContext vmc = (FileVMContext)update.getElement();
        File[] files =  vmc.getFile().listFiles();
        return files != null ? files : EMPTY_FILE_LIST;
    }

    /**
     * Returs the text label to show in the tree for given element.
     */
    private  String getLabel(FileVMContext vmc) {
        return vmc.getFile().getName();     
    }

    @Override
    public void getContextsForEvent(VMDelta parentDelta, Object event, DataRequestMonitor<IVMContext[]> rm) {
        rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "", null)); //$NON-NLS-1$
        rm.done();
    }
    
    @Override
    public int getDeltaFlags(Object e) {
        /*
         * @see buildDelta()
         */
        int retVal = IModelDelta.NO_CHANGE;
        if (e instanceof String) {
            retVal |= IModelDelta.SELECT | IModelDelta.EXPAND; 
        }
        
        return retVal;
    }

    @Override
    public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
        /*
         * The FileLayoutNode is recursive, with itself as the only child.  In this 
         * method the delta is calculated for a full path VMContext elements, and the
         * implementation of this method is not recursive.
         */
        if (event instanceof String) {
            new Job("") { //$NON-NLS-1$
                {
                    setSystem(true);
                    setPriority(INTERACTIVE);
                }
                    
                @Override
                protected IStatus run(IProgressMonitor monitor) {
                    /*
                     * Requirements for a selection event to be issued is that the file exist, and
                     * that the parentDelta contain a FileVMC of a parent directory as its element.
                     *  
                     * The test for first the former requirement could be performed inside getDeltaFlags() 
                     * but getDeltaFlags() is synchronous, so it is better to perform this test here using 
                     * a background thread (job).
                     * 
                     *  The latter is requirement is needed because this node does not have the algorithm 
                     *  calculate the complete list of root nodes.  That algorithm is implemented inside the
                     *  {@link FileSystemRootsLayoutNode#updateElements} method.
                     */
                    
                    final File eventFile = new File((String)event);
                    File parentFile = null;
                    if (parentDelta.getElement() instanceof FileVMContext) {
                        parentFile = ((FileVMContext)parentDelta.getElement()).getFile();
                    }

                    // The file has to exist in order for us to be able to select 
                    // it in the tree. 
                    if (eventFile.exists() && parentFile != null) {
                        // Create a list containing all files in path
                        List<File> filePath = new LinkedList<File>();
                        for (File file = eventFile; file != null && !file.equals(parentFile); file = file.getParentFile()) {
                            filePath.add(0, file);
                        }

                        if (filePath.size() != 0) {
                            // Build the delta for all files in path.
                            ModelDelta delta = parentDelta;
                            File[] allFilesInDirectory = parentFile.listFiles();
                            for (File pathSegment : filePath) {
                                // All files in path should be directories, and should therefore
                                // have a valid list of elements.
                                assert allFilesInDirectory != null;
                                
                                File[] pathSegmentDirectoryFiles = pathSegment.listFiles();
                                delta = delta.addNode(
                                    new FileVMContext(FileVMNode.this, pathSegment), 
                                    nodeOffset + Arrays.asList(allFilesInDirectory).indexOf(pathSegment), 
                                    IModelDelta.NO_CHANGE, 
                                    pathSegmentDirectoryFiles != null ? pathSegmentDirectoryFiles.length : 0);
                                allFilesInDirectory = pathSegmentDirectoryFiles;
                            }
                            
                            // The last file in path gets the EXPAND | SELECT flags.
                            delta.setFlags(delta.getFlags() | IModelDelta.SELECT | IModelDelta.EXPAND);
                        }
                    }
                    
                    // Invoke the request monitor.
                    
                    requestMonitor.done();

                    return Status.OK_STATUS;
                }
            }.schedule();
        } else {
            requestMonitor.done();
        }            
    }
    
    /**
     * Override the behavior which checks for delta flags of all the child nodes, 
     * because we would get stuck in a recursive loop.  Instead call only the child 
     * nodes which are not us.
     */
    protected Map<IVMNode, Integer> getChildNodesWithDeltas(Object e) {
        Map<IVMNode, Integer> nodes = new HashMap<IVMNode, Integer>(); 
        for (final IVMNode childNode : getChildNodes()) {
            int delta = childNode.getDeltaFlags(e);
            if (delta != IModelDelta.NO_CHANGE) {
                nodes.put(childNode, delta);
            }
        }
        return nodes;
    }

    @Override
    public IVMProvider getVMProvider() {
        return fProvider;
    }

    
}
